using System;
using System.Collections.Generic;
using System.Net;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace TALoaders
{
    public class CachedDownloader : BaseLoader
    {
        private class Cache
        {
            private Dictionary<string, SortedList<DateTime, Quote>> cacheM = new Dictionary<string, SortedList<DateTime, Quote>>();

            public bool TryGetValue(string key, out SortedList<DateTime, Quote> value)
            {
                return cacheM.TryGetValue(key, out value);
            }
            public void Clear()
            {
                cacheM.Clear();
            }
            public void Add(string key, SortedList<DateTime, Quote> value)
            {
                cacheM.Add(key, value);
            }
            public int nOfElements()
            {
                int n = 0;
                foreach (KeyValuePair<string, SortedList<DateTime, Quote>> pair in cacheM)
                    n += pair.Value.Count;
                return n;
            }
            public void saveToFile(string fileName)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("Key,Date,Open,High,Low,Close,Volume,AdjClose");
                foreach (KeyValuePair<string, SortedList<DateTime, Quote>> cPair in cacheM)
                {
                    foreach (KeyValuePair<DateTime, Quote> vPair in cPair.Value)
                    {
                        Quote q = vPair.Value;
                        sb.Append(cPair.Key + "," + vPair.Key.ToString());
                        sb.Append("," + q.getValue(QuoteType.Open));
                        sb.Append("," + q.getValue(QuoteType.High));
                        sb.Append("," + q.getValue(QuoteType.Low));
                        sb.Append("," + q.getValue(QuoteType.Close));
                        sb.Append("," + q.getValue(QuoteType.Volume));
                        sb.Append("," + q.getValue(QuoteType.AdjClose));
                        sb.AppendLine();
                    }
                }
                File.WriteAllText(fileName, sb.ToString());
            }
            public void openFromFile(string fileName)
            {
                cacheM.Clear();
                string[] lines = File.ReadAllText(fileName).Split('\n');
                bool isFirstLine = true;
                foreach (string line in lines)
                {
                    string[] values = line.Split(',');
                    if (values.Length == 8 && !isFirstLine)
                    {
                        string key = values[0];
                        DateTime date = Convert.ToDateTime(values[1]);
                        double open = Convert.ToDouble(values[2]);
                        double high = Convert.ToDouble(values[3]);
                        double low = Convert.ToDouble(values[4]);
                        double close = Convert.ToDouble(values[5]);
                        double volume = Convert.ToDouble(values[6]);
                        double adjClose = Convert.ToDouble(values[7]);
                        Quote quote = new Quote(open, high, low, close, volume, adjClose);
                        if (!cacheM.ContainsKey(key))
                            cacheM.Add(key, new SortedList<DateTime, Quote>());
                        cacheM[key].Add(date, quote);
                    }
                    isFirstLine = false;
                }
            }
        }

        #region Private Members
        static private Cache cacheM = new Cache();
        private BaseLoader loaderM;
        static private TraceSwitch traceM = new TraceSwitch("CachedDownloader", "String messages in CachedDownloader");
        #endregion

        #region Public Interface
        public CachedDownloader(BaseLoader loader)
            : base(loader.Loader)
        {
            loaderM = loader;
        }

        override public SortedList<DateTime, Quote> getHistoricalPrices(string ticker, DateTime startDate, DateTime endDate)
        {
            Debug.WriteLineIf(traceM.TraceInfo, "--> CachedDownloader.getHistoricalPrices(" + ticker + "," + startDate.ToShortDateString() + ", " + endDate.ToShortDateString() + ")");
            Debug.Indent();

            // Make sure that startDate and endDate do point to week days
            startDate = Utilities.thisOrPrevWeekDay(startDate);
            endDate = Utilities.thisOrPrevWeekDay(endDate);

            SortedList<DateTime, Quote> data;
            SortedList<DateTime, Quote> result;
            if (this.tryGetCache(ticker, out data))
            {
                DateTime first = data.Keys[0];
                DateTime last = data.Keys[data.Count - 1];
                Debug.WriteLineIf(traceM.TraceInfo, "Found " + data.Count + " entries. first=" + first.ToString() + ", last=" + last.ToString());
                if (startDate.Date < first.Date)
                {
                    Debug.WriteLineIf(traceM.TraceInfo, "Downloading data from " + startDate + " to " + first.AddDays(-1));
                    SortedList<DateTime, Quote> preData = new SortedList<DateTime, Quote>();
                    try
                    { // Sometimes only holiday days are missing and the download fails. Ignore it then.
                        preData = loaderM.getHistoricalPrices(ticker, startDate, first.AddDays(-1));
                    }
                    catch (WebException wex)
                    {
                        Debug.WriteLineIf(traceM.TraceError, "preData: CachedDownloader failed to download from " + last.AddDays(1).ToShortDateString() + " to " + endDate + ". IGNORING exception: " + wex.ToString());
                    }
                    foreach (KeyValuePair<DateTime, Quote> pair in preData)
                        data[pair.Key] = pair.Value;
                }
                if (endDate.Date > last.Date && endDate.Date < System.DateTime.Now.Date)
                {
                    Debug.WriteLineIf(traceM.TraceInfo, "Downloading data from " + last + " to " + endDate);
                    SortedList<DateTime, Quote> postData = new SortedList<DateTime, Quote>();
                    try
                    {
                        postData = loaderM.getHistoricalPrices(ticker, last.AddDays(1), endDate);
                    }
                    catch (WebException wex)
                    {   // Sometimes yeasterday's data are missing and we should not exit because this problem. Catch exception.
                        Debug.WriteLineIf(traceM.TraceError, "postData: CachedDownloader failed to download from " + last.AddDays(1).ToShortDateString() + " to " + endDate + ". IGNORING exception: " + wex.ToString());
                    }
                    foreach (KeyValuePair<DateTime, Quote> pair in postData)
                        data[pair.Key] = pair.Value;
                }
                if (endDate < first || startDate > last)
                    throw new Exception("Not overlaping intervals not implemented in CachedDownloader...");

                // Copy the results from data array (from startDate to endDate)
                result = new SortedList<DateTime, Quote>();
                foreach (KeyValuePair<DateTime, Quote> pair in data)
                    if (pair.Key >= startDate && pair.Key <= endDate)
                        result.Add(pair.Key, pair.Value);
            }
            else // The key does not exist. Load data from loaderM
            {
                string key = createKey(ticker);
                Debug.WriteLineIf(traceM.TraceInfo, "Key : " + key + " not found in cache. Downloading from the loader");
                result = loaderM.getHistoricalPrices(ticker, startDate, endDate);
                cacheM.Add(key, result);
            }
            Debug.Unindent();
            return result;
        }
        override public double getLastPrice(string ticker, out string timeStamp)
        {
            return loaderM.getLastPrice(ticker, out timeStamp);
        }
        #endregion

        #region Private Members
        private string createKey(string ticker)
        {
            return loaderM.Loader.ToString() + ":" + ticker;
        }
        private bool tryGetCache(string ticker, out SortedList<DateTime, Quote> quotes)
        {
            string key = createKey(ticker);
            Debug.WriteLineIf(traceM.TraceInfo, "Trying to get cache for " + key);

            return cacheM.TryGetValue(key, out quotes);
        }
        #endregion

        #region Static Public interface: Cache handling
        /// <summary>Only for testing: number of cached tikers</summary>
        /// <returns>number of cached tickers</returns>
        public int getCacheSize()
        {
            return cacheM.nOfElements();
        }
        static public void clearCache()
        {
            Debug.WriteLineIf(traceM.TraceVerbose, "Clearing cache");
            cacheM.Clear();
        }
        static public void saveCacheToFile(string fileName)
        {
            Debug.WriteLineIf(traceM.TraceVerbose, "Saving to cache-file " + fileName);
            cacheM.saveToFile(fileName);
        }
        static public void openCacheFromFile(string fileName)
        {
            cacheM.openFromFile(fileName);
        }
        #endregion
    }
}
